/// 2D测试工装的点数据和3D影像图一起投影到 -> dicom 2D 图
/// ydms_BrainOpSimuData /works/src_works/z_make/testdata/head_s2 1000
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <bits/exception.h>

#include <Eigen/Core>

#include <memath/MeVtkMath.h>

#include <vtkDICOMImageReader.h>
#include <vtkImageData.h>
#include <vtkImageViewer2.h>
#include <vtkInteractorStyleImage.h>
#include <vtkInteractorObserver.h>
#include <vtkObjectFactory.h>
#include <vtkTextMapper.h>
#include <vtkTextProperty.h>
#include <vtkActor2D.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkImageCast.h>
#include <vtkPNGWriter.h>
#include <itkImageFileWriter.h>

#include "itkMetaDataObject.h"
#include <itkGDCMImageIO.h>
#include <itkImageToVTKImageFilter.h>
#include <itkRescaleIntensityImageFilter.h>
#include <itkVTKImageToImageFilter.h>

using namespace std;

/**
二维精度工装数据
# 反光片的位置
ptsRuler2DLightBall_W:
  - "122.310000, 28.580000, 19.300000"
  - "170.070000, 76.340000, 19.300000"
  - "142.400000, 104.010000, 81.560000"
  - "98.410000, 52.490000, 73.100000"

# calibbox2DCt 上两个pose的小钢球
ptsCBPose1IronBallTop_W:
  - "197.65, 143.65, 18.23"
  - "197.65, 180.42, 55"
  - "197.65, 143.65, 91.77"
  - "197.65, 106.88, 55"
  - "197.65, 143.65, 28.23"

ptsCBPose1IronBallBot_W:
  - "119.65, 110.15, 85"
  - "119.65, 177.15, 92"
  - "119.65, 177.15, 18"
  - "119.65, 110.15, 25"
  - "119.65, 105.20, 89.95"
  - "119.65, 122.09, 86.25"

ptsCBPose2IronBallTop_W:
  - "55, 1, 18.23"
  - "91.77, 1, 55"
  - "55, 1, 91.77"
  - "18.23, 1, 55"
  - "55, 1, 28.23"

ptsCBPose2IronBallBot_W:
  - "21.48, 79, 85"
  - "88.5, 79, 92"
  - "88.5, 79, 18"
  - "21.5, 79, 25"
  - "16.55, 79, 89.95"
  - "33.44, 79, 86.25"

测试小球的位置
点28	   118.95, 79.10, 101
点29	   73.39, 125.27, 71
点30	   36.62, 125.27, 41
点31	   73.39, 162.04, 41
**/
vector<Eigen::Vector3d> ptsRuler2DLightBall_W = {
    {122.310000, 28.580000, 19.300000},
    {170.070000, 76.340000, 19.300000},
    {142.400000, 104.010000, 81.560000},
    {98.410000, 52.490000, 73.100000},
};

/// 4个测试小球的位置
vector<Eigen::Vector3d> ptsTestIron = {
    {118.95, 79.10, 101},
    {73.39, 125.27, 71 },
    {36.62, 125.27, 41 },
    {73.39, 162.04, 41 },
};

vector<Eigen::Vector3d> sPose1PtsIron_W = {
    {197.65, 143.65, 18.23},
    {197.65, 180.42, 55},
    {197.65, 143.65, 91.77},
    {197.65, 106.88, 55},
    {197.65, 143.65, 28.23},
    // bot
    {119.65, 110.15, 85},
    {119.65, 177.15, 92},
    {119.65, 177.15, 18},
    {119.65, 110.15, 25},
    {119.65, 105.20, 89.95},
    {119.65, 122.09, 86.25},
};

vector<Eigen::Vector3d> sPose2PtsIron_W = {
    {55, 1, 18.23},
    {91.77, 1, 55},
    {55, 1, 91.77},
    {18.23, 1, 55},
    {55, 1, 28.23},
    // bot
    {21.48, 79, 85},
    {88.5, 79, 92},
    {88.5, 79, 18},
    {21.5, 79, 25},
    {16.55, 79, 89.95},
    {33.44, 79, 86.25},
};

// 计算位置，并偏移到合适的位置
void projectionPoint(vtkMatrix4x4 *m, double p[3], double outP[2])
{
    double *elem = m->GetData();

    double &v1 = p[0];
    double &v2 = p[1];
    double &v3 = p[2];

    double Zc = v1 * elem[8] + v2 * elem[9] + v3 * elem[10]+ elem[11];

    outP[0] = (v1 * elem[0] + v2 * elem[1] + v3 * elem[2] + elem[3])/Zc;
    outP[1] = (v1 * elem[4] + v2 * elem[5] + v3 * elem[6] + elem[7])/Zc;
}

/// 创建一个二维标尺和CT之间的转化矩阵
/// center 旋转中心点
/// translation 为偏移中心点
vtkSmartPointer<vtkMatrix4x4> createMatrix_CtfrW(Eigen::Vector3d &center, Eigen::Vector3d &translation,
                                            Eigen::Vector3d &anglesInDegree)
{

    Eigen::Vector3d anglesInRadian = anglesInDegree * M_PI/ 180.0;
    // 计算旋转R
    vtkSmartPointer<vtkMatrix4x4> m = MeVtkMath::rotateZYX(anglesInRadian[0], anglesInRadian[1], anglesInRadian[2]);

    // 计算t
    Eigen::Vector3d offset = MeVtkMath::multiplyPoint(m, center);
    m->Element[0][3] = translation[0]-offset[0];
    m->Element[1][3] = translation[1]-offset[1];
    m->Element[2][3] = translation[2]-offset[2];

    vtkSmartPointer<vtkMatrix4x4> mInv = vtkMatrix4x4::New();
    vtkMatrix4x4::Invert(m, mInv);
    return mInv;
}


/// 创建一个投影矩阵P
/// center 旋转中心点
/// translation 为偏移中心点
vtkSmartPointer<vtkMatrix4x4> createPMatrix(Eigen::Vector3d &center, Eigen::Vector3d &translation,
                                            double f, double dx, double dy, double u0, double v0,
                                            Eigen::Vector3d &anglesInDegree)
{

    Eigen::Vector3d anglesInRadian = anglesInDegree * M_PI/ 180.0;
    // 计算旋转R
    vtkSmartPointer<vtkMatrix4x4> m = MeVtkMath::rotateZYX(anglesInRadian[0], anglesInRadian[1], anglesInRadian[2]);

    // 计算t
    Eigen::Vector3d offset = MeVtkMath::multiplyPoint(m, center);
    m->Element[0][3] = translation[0]-offset[0];
    m->Element[1][3] = translation[1]-offset[1];
    m->Element[2][3] = translation[2]-offset[2];

    cout << "Camera From W =";
    m->Print(cout);

    double fx = f/dx, fy = f/dy;
    m->Element[0][0] =m->Element[0][0]*fx + m->Element[2][0] * u0;
    m->Element[0][1] =m->Element[0][1]*fx + m->Element[2][1] * u0;
    m->Element[0][2] =m->Element[0][2]*fx + m->Element[2][2] * u0;
    m->Element[0][3] =m->Element[0][3]*fx + m->Element[2][3] * u0;

    m->Element[1][0] =m->Element[1][0]*fy + m->Element[2][0] * v0;
    m->Element[1][1] =m->Element[1][1]*fy + m->Element[2][1] * v0;
    m->Element[1][2] =m->Element[1][2]*fy + m->Element[2][2] * v0;
    m->Element[1][3] =m->Element[1][3]*fy + m->Element[2][3] * v0;

    cout << "projection Matrix =";
    m->Print(cout);

    return m;
}

using PixelType = double;
constexpr unsigned int Dimension = 2;
using ImageType = itk::Image<PixelType, Dimension>;


void getImageRange(ImageType::Pointer image, double range[2])
{
    using ConstIteratorType = itk::ImageRegionConstIterator< ImageType >;
    ConstIteratorType in( image, image->GetRequestedRegion() );
    range[0] = image->GetPixel({{0,0}});
    range[1] = range[0];

    for ( in.GoToBegin(); !in.IsAtEnd(); ++in )
    {
        if (in.Get() > range[1]) {
            range[1] = in.Get();
        } else if (in.Get() < range[0]) {
             range[0] = in.Get();
        }
    }
}

template<typename _Tp>
ImageType::Pointer projectImage(vtkImageData *volume, vtkMatrix4x4 *m, std::string &outputFilename, int w, int h, int threshold,
                                vector<Eigen::Vector3d> &ironPoints, vector<Eigen::Vector3d> &featurePoints)
{
    int dims[3];
    volume->GetDimensions(dims);
    double range[2];
    volume->GetScalarRange(range);
    double maxvalue = range[1] - threshold;
    double spacing[3];
    volume->GetSpacing(spacing);


    // itk image init
    ImageType::RegionType region;
    ImageType::IndexType  start;
    start[0] = 0;
    start[1] = 0;

    ImageType::SizeType size;
    size[0] = w;
    size[1] = h;

    region.SetSize(size);
    region.SetIndex(start);

    ImageType::Pointer image = ImageType::New();
    image->SetRegions(region);

    image->Allocate();
    image->FillBuffer(0);
    // << image init

    double minX =0, maxV = 0;

    _Tp* pData = static_cast<_Tp*>(volume->GetScalarPointer());
    for (int z = 0; z < dims[2]; z++)
    {
        for (int y = 0; y < dims[1]; y++)
        {
            for (int x = 0; x < dims[0]; x++)
            {
                _Tp pixel = pData[x + y*dims[0] + z*dims[0]*dims[1]];
                if (pixel < threshold) {
                    continue;
                }
                //unsigned char newpixel = 255*(pixel - threshold)/maxvalue;
                double newpixel = pixel;
                double p[3] = {double(x*spacing[0]), double(y*spacing[1]), double(z*spacing[2])};
                double out[2];
                projectionPoint(m, p, out);
                // 取图像中最大值作为投影
                int ox = round(out[0]), oy = round(out[1]);
                if (ox >= 0 && ox<w && oy >= 0 && oy < h) {
                    ImageType::IndexType pixelIndex = { { ox, oy } };
                    ImageType::PixelType pv2 = image->GetPixel(pixelIndex);
                    image->SetPixel(pixelIndex, newpixel+pv2);
                    if (newpixel+pv2 > maxV) {
                        maxV = newpixel+pv2;
                    }
                }
            }
            //std::cout << std::endl;
        }
        //std::cout << std::endl;
    }

    // 加入投影小球
    maxV += 1000;
    const double r = 13, br = 26;
    double r2 = r*r, br2 = br*br;
    cout << "Project ironball to uv plane: " << endl;
    for (uint i=0; i < ironPoints.size(); i++) {
        double outP[2];
        projectionPoint(m, ironPoints[i].data(), outP);
        cout << "    w->uv: "<< ironPoints[i].transpose() << " to " << outP[0] << "," << outP[1] << endl;
        double win = r+2;
        double cr2 = r2;
//        if (i==11 || i== 12 || i== 13) {
//            win = br+2;
//            cr2 =  br2;
//        }
        for (int x=outP[0]-w; x< outP[0] + w; x++) {
            for (int y=outP[1]-w; y< outP[1] + w; y++) {
                if ((x-outP[0])*(x-outP[0]) + (y-outP[1])*(y-outP[1]) <= cr2) {
                    if (x >= 0 && x<w && y >= 0 && y < h) {
                        ImageType::IndexType pixelIndex = { { x, y } };
                        image->SetPixel(pixelIndex, maxV);
                    }
                }
            }
        }
    }
    cout << "===" << endl;

    // 特征点
    cout << "Project featurePoints to uv plane: " << endl;
    for (uint i=0; i < featurePoints.size(); i++) {
        double outP[2];
        projectionPoint(m, featurePoints[i].data(), outP);
        cout << "    w->uv: "<< featurePoints[i].transpose() << " to " << outP[0] << "," << outP[1] << endl;
        double win = r+2;
        double cr2 = r2;
        if (i < 3) {
            win = br+2;
            cr2 =  br2;
        }
        for (int x=outP[0]-w; x< outP[0] + w; x++) {
            for (int y=outP[1]-w; y< outP[1] + w; y++) {
                if ((x-outP[0])*(x-outP[0]) + (y-outP[1])*(y-outP[1]) <= cr2) {
                    if (x >= 0 && x<w && y >= 0 && y < h) {
                        ImageType::IndexType pixelIndex = { { x, y } };
                        image->SetPixel(pixelIndex, maxV);
                    }
                }
            }
        }
    }
    cout << "===" << endl;

    using IteratorType = itk::ImageRegionIterator< ImageType >;
    IteratorType in( image, image->GetRequestedRegion() );
    for ( in.GoToBegin(); !in.IsAtEnd(); ++in )
    {
        in.Set(maxV - in.Get());
    }

    return image;
}

struct CameraParam {
    CameraParam() {
        f = 1000;
        dx = 1;
        dy = 1;
        u0 = 0;
        v0 = 0;
        w = 400;
        h = 400;
    }
    double f, dx, dy, u0, v0;
    int w, h;
};

/// 投影图像 cameraParams = [f, dx, dy, u0, v0, w, h]
ImageType::Pointer projectImage(vtkImageData *volume, Eigen::Vector3d &center, Eigen::Vector3d &translation, Eigen::Vector3d &angInDegree,
                                           CameraParam &params, double threshold, std::string &outfolder)
{
    cout << "params: center=" << center.transpose()
         << ", tanslation=" << translation.transpose()
         << ", angle=" << angInDegree.transpose()
         << endl;
    vtkSmartPointer<vtkMatrix4x4> m = createPMatrix(center, translation, params.f, params.dx, params.dy, params.u0, params.v0, angInDegree);

    Eigen::Vector3d center2 = {0, 0, 0};
    Eigen::Vector3d translation2 = {-70, 270, 130};
    Eigen::Vector3d angInDegree2 = {180, 0, 0};
    vtkSmartPointer<vtkMatrix4x4> m_CtfrW = createMatrix_CtfrW(center2, translation2, angInDegree2);
    cout << "m_CtfrW = ";
    m_CtfrW->Print(cout);
    vector<Eigen::Vector3d> &srcPts = abs(angInDegree[1])>45.0 ? sPose1PtsIron_W:sPose2PtsIron_W;
    vector<Eigen::Vector3d> ironPoints(sPose1PtsIron_W.size());
    for(uint i=0; i< srcPts.size(); i++) {
        ironPoints[i] = MeVtkMath::multiplyPoint(m_CtfrW, srcPts[i]);
    }

    cout << "反光片的位置 :" << endl;
    for (uint i=0; i< ptsRuler2DLightBall_W.size(); i++) {
        Eigen::Vector3d p = MeVtkMath::multiplyPoint(m_CtfrW, ptsRuler2DLightBall_W[i]);
        cout << "   " << p[0] << ", " << p[1] << ", " << p[2] << endl;
    }
    cout << "===" << endl;

    cout << "测试小球的位置 :" << endl;
    for (uint i=0; i< ptsTestIron.size(); i++) {
        Eigen::Vector3d p = MeVtkMath::multiplyPoint(m_CtfrW, ptsTestIron[i]);
        cout << "   " << p[0] << ", " << p[1] << ", " << p[2] << endl;
    }
    cout << "===" << endl;

    vector<Eigen::Vector3d> featurePoints;
    if(1){ // 加入3个固定小球点
        // double spacing[3];
        // volume->GetSpacing(spacing);
        double spacing[3] = {0.488281, 0.488281, 0.625};
        featurePoints.push_back({204.59, 113.769,  136.25}); // 左耳朵
        featurePoints.push_back({56.6406, 103.027, 144.375}); // 右耳朵
        featurePoints.push_back({108.398, 215.8, 150.625}); // 鼻子（大）
        featurePoints.push_back({145.996, 215.82, 150.625}); // 鼻子（小）
    }

    char buf[200];
    snprintf(buf, 200, "%s/proj_%03d_%03d.png", outfolder.c_str(), int(angInDegree[1]), int(angInDegree[0]));
    std::string outputFilename = buf;

    ImageType::Pointer img;
    switch (volume->GetScalarType()) {
    case VTK_SHORT:
        img = projectImage<short>(volume, m, outputFilename, params.w, params.h, threshold, ironPoints, featurePoints);
        break;
    default:
        //img = projectImage<unsigned char>(volume, m, outputFilename, params.w, params.h);
        break;
    }

    return img;
}

int writeToDicomFile(ImageType::Pointer imageData, Eigen::Vector3d &angInDegree, std::string &outfolder)
{
    char buf[200];
    snprintf(buf, 200, "%s/proj_%03d_%03d.dcm", outfolder.c_str(), int(angInDegree[1]), int(angInDegree[0]));
    std::string outputFilename = buf;

    using OutputImageType = itk::Image<unsigned short, 2>;
    using FilterType = itk::RescaleIntensityImageFilter<ImageType, OutputImageType>;
    FilterType::Pointer filter = FilterType::New();

    filter->SetOutputMinimum(0);
    filter->SetOutputMaximum(64000);
    filter->SetInput(imageData);


    using ImageIOType = itk::GDCMImageIO;
    ImageIOType::Pointer gdcmIO = ImageIOType::New();
    itk::MetaDataDictionary & dict = gdcmIO->GetMetaDataDictionary();
    {
        std::string               tagkey, value;
        tagkey = "0008|0060"; // Modality
        value = "MR";
        itk::EncapsulateMetaData<std::string>(dict, tagkey, value);
        tagkey = "0028|0004"; // Conversion Type
        value = "MONOCHROME2";
        itk::EncapsulateMetaData<std::string>(dict, tagkey, value);
    }

    using WriterType = itk::ImageFileWriter<OutputImageType>;
    WriterType::Pointer writer = WriterType::New();
    writer->SetFileName(outputFilename);
    writer->SetInput(filter->GetOutput());
    writer->SetImageIO(gdcmIO);
    //writer->SetMetaDataDictionary(dict);

    try
    {
        writer->Update();
        std::cout << "Write to " << outputFilename << std::endl;
    }
    catch (itk::ExceptionObject & error)
    {
        std::cerr << "Error: " << error << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

// helper class to format slice status message
class StatusMessage {
public:
    static std::string format(int angleX, int angleY) {
        std::stringstream tmp;
        tmp << "Rotate(X/Y): " << angleX << "/" << angleY;
        return tmp.str();
    }
    static std::string format(Eigen::Vector3d &angles) {
        std::stringstream tmp;
        tmp << "Rotate(X/Y/Z): " << angles[0] << "/" << angles[1] << "/" << angles[2];
        return tmp.str();
    }
};

void updateImageViewData(vtkImageViewer2 *imageViewer, ImageType::Pointer image)
{
    typedef itk::ImageToVTKImageFilter<ImageType> ConnectorType;  //VTK和ITK链接器

    ConnectorType::Pointer connector = ConnectorType::New();
    connector->SetInput(image);
    connector->Update();

    double range2[2] = {0, 40000};
    getImageRange(image, range2);
    imageViewer->SetInputData( connector->GetOutput());
    imageViewer->SetColorWindow(range2[1]-range2[0]);
    imageViewer->SetColorLevel(range2[1]/2);

    double window = imageViewer->GetColorWindow();
    double level = imageViewer->GetColorLevel();
    cout << "window: " << window << " level: " << level << endl;
}

// Define own interaction style
class SliceVtkInteractorStyleImage : public vtkInteractorStyleImage
{
public:
    static SliceVtkInteractorStyleImage* New();
    vtkTypeMacro(SliceVtkInteractorStyleImage, vtkInteractorStyleImage);

public:
    vtkImageData * volume;
    vtkImageViewer2* imageViewer;
    vtkTextMapper* statusMapper;
    ImageType::Pointer imageData; // 记录当前imageData

    Eigen::Vector3d center = {0, 0, 0};
    Eigen::Vector3d translation = {0, 0, 1000};
    Eigen::Vector3d angInDegree = {0, 0, 0};
    CameraParam params;
    double threshold = 500;
    int step = 1;
    std::string outputFolder = "./test";

public:
    void setVolume(vtkImageData *_volume) {
        volume = _volume;
    }
    void setImageViewer(vtkImageViewer2* _imageViewer) {
        imageViewer = _imageViewer;
    }

    void setStatusMapper(vtkTextMapper* _statusMapper) {
        statusMapper = _statusMapper;
    }

    void swithImage() {
        imageData = projectImage(volume, center, translation, angInDegree, params, threshold, outputFolder);
        std::string msg = StatusMessage::format(angInDegree);
        statusMapper->SetInput(msg.c_str());
        updateImageViewData(imageViewer, imageData);
        // imageViewer->SetInputData(imageData);
        imageViewer->Render();
    }


protected:
    void moveLeft() {
        angInDegree[0] -= step;
        swithImage();
    }
    void moveRight() {
        angInDegree[0] += step;
        swithImage();
    }
    void moveUp() {
        angInDegree[1] -= step;
        swithImage();
    }
    void moveDown() {
        angInDegree[1] += step;
        swithImage();
    }

    void rotateZP() {
        angInDegree[2] += step;
        swithImage();
    }

    void rotateZN() {
        angInDegree[2] -= step;
        swithImage();
    }

    virtual void OnKeyDown() override {
        std::string key = this->GetInteractor()->GetKeySym();
        if(key.compare("Left") == 0) {
            moveLeft();
        } else if(key.compare("Right") == 0) {
            moveRight();
        } else if(key.compare("Up") == 0) {
            moveUp();
        } else if(key.compare("Down") == 0) {
            moveDown();
        } else if(key.compare("o") == 0) {
            rotateZP();
        } else if (key.compare("p") == 0) {
            rotateZN();
        } else if (key.compare("r") == 0) {
            angInDegree[2] = 0;
            swithImage();
        } else if (key.compare("s") == 0) { // save image
            imageData = projectImage(volume, center, translation, angInDegree, params, threshold, outputFolder);
            writeToDicomFile(imageData, angInDegree, outputFolder);
        }
        // forward event
        vtkInteractorStyleImage::OnKeyDown();
    }

    virtual void OnMouseMove() override {
        // forward event
        vtkInteractorStyleImage::OnMouseMove();
    }
};

vtkStandardNewMacro(SliceVtkInteractorStyleImage);

int main(int argc, char *argv[])
{
    if(argc < 3)
    {
      std::cerr << "Required arguments: dicomfolder threshold" << std::endl;
      return EXIT_FAILURE;
    }
    std::string inputFilename = argv[1];
    double threshold = atof(argv[2]);

    vtkSmartPointer<vtkDICOMImageReader> readerDcm = vtkSmartPointer<vtkDICOMImageReader>::New();
    vtkImageData *volume = nullptr;

    readerDcm->SetDirectoryName(inputFilename.c_str());
    readerDcm->Update();
    volume = readerDcm->GetOutput();

    cout << "image info: " << endl;
    double origin[3];
    volume->GetOrigin(origin);
    cout << "origin: " << origin[0] << ", " << origin[1] << ", " << origin[2] << endl;
    int dims[3];
    volume->GetDimensions(dims);
    cout << "dimensions: " << dims[0] << ", " << dims[1] << ", " << dims[2] << endl;
    double spacing[3];
    volume->GetSpacing(spacing);
    cout << "spacing: " << spacing[0] << ", " << spacing[1] << ", " << spacing[2] << endl;
    cout << endl;
    cout << "real size(mm):" << dims[0]*spacing[0] << ", " << dims[1]*spacing[1]
            << ", " << dims[2]*spacing[2] << endl;

    // 建立投影矩阵
    cout << "===创建投影矩阵===" << endl;
    Eigen::Vector3d center = {dims[0]*spacing[0]/2, dims[1]*spacing[1]/2, dims[2]*spacing[2]/2};
    Eigen::Vector3d translation = {0, 0, 900};
    Eigen::Vector3d angleInDegree = {90, 0, 0};

    CameraParam params;
    params.w = 2064;
    params.h = 2064;
    params.u0 = params.v0 = (params.w-1)/2.0;
    params.f = 9000;
    params.dx = params.dy = 1;


    // first ->
    std::string outputFolder = "./test";
    if (0 != access(outputFolder.c_str(), W_OK)) {
        mkdir(outputFolder.c_str(), 0777);
    }

    ImageType::Pointer image = projectImage(volume, center, translation, angleInDegree, params, threshold, outputFolder);

    // Visualize
    vtkSmartPointer<vtkImageViewer2> imageViewer = vtkSmartPointer<vtkImageViewer2>::New();
    updateImageViewData(imageViewer, image);
    // image->GetScalarRange(range2);

    // slice status message
    vtkSmartPointer<vtkTextProperty> sliceTextProp = vtkSmartPointer<vtkTextProperty>::New();
    sliceTextProp->SetFontFamilyToCourier();
    sliceTextProp->SetFontSize(20);
    sliceTextProp->SetVerticalJustificationToBottom();
    sliceTextProp->SetJustificationToLeft();

    vtkSmartPointer<vtkTextMapper> sliceTextMapper = vtkSmartPointer<vtkTextMapper>::New();
    std::string msg = StatusMessage::format(angleInDegree);
    sliceTextMapper->SetInput(msg.c_str());
    sliceTextMapper->SetTextProperty(sliceTextProp);

    vtkSmartPointer<vtkActor2D> sliceTextActor = vtkSmartPointer<vtkActor2D>::New();
    sliceTextActor->SetMapper(sliceTextMapper);
    sliceTextActor->SetPosition(15, 10);

    // create an interactor with our own style (inherit from vtkInteractorStyleImage)
    // in order to catch mousewheel and key events
    vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
            vtkSmartPointer<vtkRenderWindowInteractor>::New();

    vtkSmartPointer<SliceVtkInteractorStyleImage> sliceInteractorStyle =
            vtkSmartPointer<SliceVtkInteractorStyleImage>::New();

    sliceInteractorStyle->setVolume(volume);
    sliceInteractorStyle->setImageViewer(imageViewer);
    sliceInteractorStyle->setStatusMapper(sliceTextMapper);
    sliceInteractorStyle->params = params;
    sliceInteractorStyle->step = 45;
    sliceInteractorStyle->threshold = threshold;
    sliceInteractorStyle->center = center;
    sliceInteractorStyle->translation = translation;
    sliceInteractorStyle->angInDegree = angleInDegree;

    imageViewer->SetupInteractor(renderWindowInteractor);

    renderWindowInteractor->SetInteractorStyle(sliceInteractorStyle);
    imageViewer->GetRenderer()->AddActor2D(sliceTextActor);

    // initialize rendering and interaction
    imageViewer->GetRenderWindow()->SetSize(800, 800);
    imageViewer->GetRenderer()->SetBackground(0.2, 0.3, 0.4);
    imageViewer->Render();
    imageViewer->GetRenderer()->ResetCamera();
    imageViewer->Render();
    renderWindowInteractor->Start();

    return 0;
}
